#pragma once

#include "AttendanceListener.h"
#include "GameNotReady.h"
#include "GameState.h"

/*
	The class responsible for player attendance and identification,
	GameState maintenance and advancement, and finally the initiation and completion of games.

	Each game project should only include one class descended from GameEngine,
	as it behaves like a Singleton -- but more importantly, its component GameStates
	have static members which cannot be shared by multiple games simultaneously.

	GameEngine has been written so that specific game implementations can be extended from it,
	but the recommendation is to do most of the revisions as children of GameState.
	GameEngine consults GameState's NONE and INITIAL properties to determine where
	a game ends and begins, respectively.

	The one exception to this recommendation are the minimumNumberOfPlayers()
	and maximumNumberOfPlayers() methods.  They are currently implemented to return 1 and 4.
	Until the design of this class is revised, the only way to change those values
	is through inheritance.  Be sure to set GameEngine::singleton to point to your version
	of the engine in any case.

	The procedure towards getting a game to start is to seat() four GamePlayer objects,
	then invoke begin(). GamePlayer should override startTurn() to process the incoming
	GameStates in some way, leading eventually to a GameEngine::instance().enact() command.
	GameEngine will inform all players of observeStateTransition() events
	when the variable exposed by getCurrentGameState() changes.
*/
class GameEngine {
public:
	/*
		Initialize a game to the first GameState.
		@throws GameNotReady player count is outside [min, max] range
	*/
	virtual
	void			begin();

	/*
		Accessor.
		@return	numberOfSeatedPlayers
	*/
	SeatNumber		countParticipants()
	{
		return numberOfSeatedPlayers;
	} // end method

	/*
		Move the game into an adjacent state, if the entity making the request
		is authorized to do so.
		@param state		a forward path not yet taken, but intended to be taken
		@param credential	an item that confirms that the command-to-move has authority
	*/
	virtual
	void			enact(
						GameState* state,
						Authority* credential
					);

	/*
		Terminate a game in progress.
		currentState will be set to GameState::NONE as a result.
	*/
	virtual
	void			end();

	/*
		Determine which position, ranging between 0 and maximumNumberOfPlayers()-1,
		is not currently filled with a GamePlayer object.
		@return		index around a virtual table; there is no real "starting" location
					though the response proceeds forward from zero
					if a seat() command occurs, the number returned here
					will be where the GamePlayer is seated
	*/
	virtual
	SeatNumber		findEmptySeat();

	/*
		Find out all presently-active data about the game.
		@return		GameState reference; see class header for details
	*/
	GameState*		getCurrentState() {
						return currentState;
					} // end method

	/*
		Second-level accessor that retrieves the GamePlayer
		at the specified seat index.
		@return		object that models that player,
					unless the seat is vacant (NULL)
	*/
	GamePlayer*		identifyParticipant(int seatNumber)
					{
						return participants[seatNumber];
					} // end method

	/*
		Companion method to identifyParticipant.
		Given a player-identifying object, finds out their index
		around this game's virtual table (unless they're not seated).
		@param presumedParticipant	object to search for
		@return						matching index of the participants attribute
		@throws runtime_error		"no match" (player is not seated)
	*/
	SeatNumber		locateParticipant(GamePlayer*);

	/*
		Singleton-pattern gateway method.
		@return		the only GameEngine object allowed in your project
	*/
	static
	GameEngine&		instance()
					{
						return *GameEngine::singleton;
					} // end static method

	/*
		Determine if the game that this engine manages is "in progress".
		Effectively false until begin() is invoked successfully, prior to a corresponding end().
		@return		currentState is not NULL -- due to this criterion,
					even games that have interactive setup, such as Chess ("who will be White?")
					are considered underway immediately after participants have been established
	*/
	bool			isUnderway()
					{
						return (GameState::NONE == currentState);
					} // end method

	/*
		Attribute-free property accessor.
		Enforces a lower bound on a participant count.  begin() fails otherwise.
		@return		1, by default; override in your implementation of a specific game
	*/
	virtual
	SeatNumber		minimumNumberOfPlayers();

	/*
		Attribute-free property accessor.
		Enforces an upper bound on a participant count.  begin() fails otherwise.
		@return		4, by default; override in your implementation of a specific game
	*/
	virtual
	SeatNumber		maximumNumberOfPlayers();

	/*
		Observer-pattern.  Allows externals to become aware of seat() and vacate() commands.
		@param l	see AttendanceListener
	*/
	void			registerAttendanceListener(AttendanceListener* l)
					{
						attendanceListeners.push_back(l);
						l->participantCountSet(maximumNumberOfPlayers());
					} // end method

	/*
		In theory, this method allows the Engine to redact a list of adjacent possibilities.
		Usually, this would amount to removal of passed-in elements.
		You may want to do this when the Engine knows something about the active player
		that the GameStates don't know.

		This feature's relevance is in doubt, and it may be revoked before any software release.
		@param list	all possible paths out from currentState
		@return		same list as the incoming one, with some elements removed
	*/
	StateList&		restrictActions(StateList& list) {
						return list;
					} // end method

	/*
		Add a player to the list of participants in the game if there is room.
		@param who	someone who is not currently playing in this game, but would like to
		@throws	GameNotReady	if the addition of this player would cause overflow
	*/
	virtual
	void			seat(GamePlayer*);

	/*
		Remove the player at the specified index.
		@param seatNumber			index at virtual table
		@throws out_of_range		when seatNumber >= max
		@throws invalid_argument	when participants[seatNumber] == NULL
									(already vacated or never occupied in the first place)
	*/
	virtual
	void			vacate(SeatNumber);

protected:
	// An object containing all the data of the game as it is, in this moment.
	GameState*		currentState;
	// Attendance count.
	SeatNumber		numberOfSeatedPlayers;

	/*
		The sole constructor ensures that no game is underway and its various
		rosters are prepped to receive players.
	*/
					GameEngine() :
						numberOfSeatedPlayers(0)
					{
						currentState = GameState::NONE;
						participants = new GamePlayer*[maximumNumberOfPlayers()];
						participantCredentials = NULL;

						resetCredentials();
					} // end constructor

	/*
		The sole destructor revokes the assigned Authority objects,
		and releases memory associated with the players.
	*/
					~GameEngine()
					{
						resetCredentials();

						delete [] participants;
						delete [] participantCredentials;
					} // end destructor

	/*
		@param creds	a security token presumed to have been bestowed by the Engine
						when the requesting object received a startTurn() signal
		@return			false when the parameter != retained participantCredentials
	*/
	virtual
	bool			authorityIsInvalid(Authority* creds);

	/*
		Moves the game from one contiguous series of GameStates to another.
		GameStates are responsible for identifying which one of them
		starts a new sequence, given the currentGameState.
		When GameState::getNextSequence responds with NULL, GameState::end() is called.
	*/
	virtual
	void			endSequence()
					{
						if (NULL == (currentState = currentState->getNextSequence()))
							end();
						else
							goadPlayers();
					} // end method

	/*
		Provoke someone to take an action.
		This is the primary operation to request game turn decisions.
	*/
	virtual
	void			goadPlayers();

	/*
		Set a player's action-choosing credentials to the specified value.
		In so doing, the old value is being discarded.  Release the memory associated with it.
		@param seatNumber	which seat is being affected by this operation [default: all]
		@param newCreds		the replacement value [default: NULL; this means we're performing cleanup]
		@return				chained parameter newCreds
	*/
	virtual
	Authority*		resetCredentials(
						SeatNumber	seat = ALL_SEATS,
						Authority*	newCreds = NULL
					);

private:
	// Reserved for future expansion.
	// This value could be the answer to the question "whose turn is it?" if the game permits
	// simultaneously play for all participants.
	static
	SeatNumber		
	const			ALL_SEATS = -1;
	// Observer objects that want to know when seat() and vacate() commands are run.
	AttendanceList	attendanceListeners;
	// Security token(s) for the active player(s).  These are given at GamePlayer::startTurn()
	// and accepted for identification purposes when enact() is invoked.
	Authority*		participantCredentials;
	// An array of players, signed up to play in the current game.
	// Their order is not assumed to be significant, though each index is a volatile ID for a player.
	GamePlayer**	participants;
	// Since only one GameEngine is permitted per project, this is where it is housed.
	static
	GameEngine*
	const			singleton;

	/*
		Follows a successful seat() command by sending out events to AttendanceListeners.
		@param seatNumber	location just given to the new player, around the virtual table
	*/
	void			introducePlayer(SeatNumber);
}; // end class
